package ingress

/*
Copyright 2021-2025 The k8gb Contributors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic
*/

import (
	"context"
	"fmt"
	"reflect"

	"github.com/k8gb-io/k8gb/controllers/refresolver/queryopts"

	k8gbv1beta1 "github.com/k8gb-io/k8gb/api/v1beta1"
	"github.com/k8gb-io/k8gb/controllers/logging"
	"github.com/k8gb-io/k8gb/controllers/utils"
	netv1 "k8s.io/api/networking/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

var log = logging.Logger()

type ReferenceResolver struct {
	ingress *netv1.Ingress
}

// NewReferenceResolver creates a reference resolver capable of understanding referenced ingresses.networking.k8s.io resources
func NewReferenceResolver(gslb *k8gbv1beta1.Gslb, k8sClient client.Client) (*ReferenceResolver, error) {
	ingressList, err := getGslbIngressRef(gslb, k8sClient)
	if err != nil {
		return nil, err
	}
	for _, ingress := range ingressList {
		log.Info().
			Str("Name", ingress.Name).
			Msg("Found Ingress")
	}

	if len(ingressList) != 1 {
		return nil, fmt.Errorf("exactly 1 Ingress resource expected but %d were found", len(ingressList))
	}

	return &ReferenceResolver{
		ingress: &ingressList[0],
	}, nil
}

// getGslbIngressRef resolves a Kubernetes Ingress resource referenced by the Gslb spec
func getGslbIngressRef(gslb *k8gbv1beta1.Gslb, k8sClient client.Client) ([]netv1.Ingress, error) {
	query, err := queryopts.Get(gslb.Spec.ResourceRef, gslb.Namespace)
	if err != nil {
		return nil, err
	}

	switch query.Mode {
	case queryopts.QueryModeGet:
		var ing = netv1.Ingress{}
		err = k8sClient.Get(context.TODO(), *query.GetKey, &ing)
		if err != nil {
			if errors.IsNotFound(err) {
				log.Info().
					Str("gslb", gslb.Name).
					Str("namespace", gslb.Namespace).
					Msg("Can't find referenced Ingress resource")
			}
			return nil, err
		}
		return []netv1.Ingress{ing}, nil

	case queryopts.QueryModeList:
		var ingList netv1.IngressList
		err = k8sClient.List(context.TODO(), &ingList, query.ListOpts...)
		if err != nil {
			if errors.IsNotFound(err) {
				log.Info().
					Str("gslb", gslb.Name).
					Str("namespace", gslb.Namespace).
					Msg("Can't find referenced Ingress resource")
			}
			return nil, err
		}
		return ingList.Items, nil
	}
	return nil, fmt.Errorf("unknown query mode %v", query.Mode)
}

// NewEmbeddedResolver creates a reference resolver capable of understanding embedded ingresses.networking.k8s.io resources
func NewEmbeddedResolver(gslb *k8gbv1beta1.Gslb, k8sClient client.Client) (*ReferenceResolver, error) {
	ingressEmbedded, err := getGslbIngressEmbedded(gslb, k8sClient)
	if err != nil {
		return nil, err
	}
	if ingressEmbedded == nil {
		return nil, fmt.Errorf("exactly 1 Ingress resource expected but none was found")
	}

	return &ReferenceResolver{
		ingress: ingressEmbedded,
	}, nil
}

// getGslbIngressEmbedded resolves a Kubernetes Ingress resource embedded in the Gslb spec
func getGslbIngressEmbedded(gslb *k8gbv1beta1.Gslb, k8sClient client.Client) (*netv1.Ingress, error) {
	if reflect.DeepEqual(gslb.Spec.Ingress, k8gbv1beta1.IngressSpec{}) {
		log.Info().
			Str("gslb", gslb.Name).
			Msg("No configuration for embedded Ingress resource")
		return nil, nil
	}

	nn := types.NamespacedName{
		Name:      gslb.Name,
		Namespace: gslb.Namespace,
	}
	ingress := &netv1.Ingress{}
	err := k8sClient.Get(context.TODO(), nn, ingress)
	if err != nil {
		if errors.IsNotFound(err) {
			log.Warn().
				Str("gslb", gslb.Name).
				Msg("Can't find gslb Ingress")
		}
		return nil, err
	}

	return ingress, nil
}

// GetServers retrieves the GSLB server configuration from the gateway resource
func (rr *ReferenceResolver) GetServers() ([]*k8gbv1beta1.Server, error) {
	servers := []*k8gbv1beta1.Server{}

	for _, rule := range rr.ingress.Spec.Rules {
		server := &k8gbv1beta1.Server{
			Host:     rule.Host,
			Services: []*k8gbv1beta1.NamespacedName{},
		}
		for _, path := range rule.HTTP.Paths {
			if path.Backend.Service == nil || path.Backend.Service.Name == "" {
				log.Warn().
					Str("ingress", rr.ingress.Name).
					Msg("Malformed service definition")
				continue
			}

			server.Services = append(server.Services, &k8gbv1beta1.NamespacedName{
				Name:      path.Backend.Service.Name,
				Namespace: rr.ingress.Namespace,
			})
		}
		servers = append(servers, server)
	}

	return servers, nil
}

// GetGslbExposedIPs retrieves the load balancer IP address of the GSLB
func (rr *ReferenceResolver) GetGslbExposedIPs(gslbAnnotations map[string]string, parentZoneDNSServers utils.DNSList) ([]string, error) {
	// fetch the IP addresses of the reverse proxy from an annotation if it exists
	if ingressIPsFromAnnotation, ok := gslbAnnotations[utils.ExternalIPsAnnotation]; ok {
		return utils.ParseIPAddresses(ingressIPsFromAnnotation)
	}

	// if there is no annotation -> fetch the IP addresses from the Status of the Ingress resource
	gslbIngressIPs := []string{}
	for _, ip := range rr.ingress.Status.LoadBalancer.Ingress {
		if len(ip.IP) > 0 {
			gslbIngressIPs = append(gslbIngressIPs, ip.IP)
		}
		if len(ip.Hostname) > 0 {
			IPs, err := utils.Dig(ip.Hostname, 8, parentZoneDNSServers...)
			if err != nil {
				log.Warn().Err(err).Msg("Dig error")
				return nil, err
			}
			gslbIngressIPs = append(gslbIngressIPs, IPs...)
		}
	}

	return gslbIngressIPs, nil
}
